/*
 * File      : poll.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2017, RT-Thread Development Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2016-12-28     Bernard      first version
 * 2018-03-09     Bernard      Add protection for pt->triggered.
 */
#include <stdint.h>

#include <rthw.h>
#include <rtdevice.h>
#include <rtthread.h>

#include <dfs.h>
#include <dfs_file.h>
#include <dfs_posix.h>
#include <dfs_poll.h>

struct rt_poll_node;

struct rt_poll_table {
	rt_pollreq_t req;
	rt_uint32_t triggered; /* the waited thread whether triggered */
	rt_thread_t polling_thread;
	struct rt_poll_node* nodes;
};

struct rt_poll_node {
	struct rt_wqueue_node wqn;
	struct rt_poll_table* pt;
	struct rt_poll_node* next;
};

static int __wqueue_pollwake(struct rt_wqueue_node* wait, void* key)
{
	struct rt_poll_node* pn;

	if(key && !((rt_uint32_t)key & wait->key))
		return -1;

	pn = rt_container_of(wait, struct rt_poll_node, wqn);
	pn->pt->triggered = 1;

	return __wqueue_default_wake(wait, key);
}

static void _poll_add(rt_wqueue_t* wq, rt_pollreq_t* req)
{
	struct rt_poll_table* pt;
	struct rt_poll_node* node;

	node = rt_malloc(sizeof(struct rt_poll_node));

	if(node == RT_NULL)
		return;

	pt = rt_container_of(req, struct rt_poll_table, req);

	node->wqn.key = req->_key;
	rt_list_init(&(node->wqn.list));
	node->wqn.polling_thread = pt->polling_thread;
	node->wqn.wakeup = __wqueue_pollwake;
	node->next = pt->nodes;
	node->pt = pt;
	pt->nodes = node;
	rt_wqueue_add(wq, &node->wqn);
}

static void poll_table_init(struct rt_poll_table* pt)
{
	pt->req._proc = _poll_add;
	pt->triggered = 0;
	pt->nodes = RT_NULL;
	pt->polling_thread = rt_thread_self();
}

static int poll_wait_timeout(struct rt_poll_table* pt, int msec)
{
	rt_int32_t timeout;
	int ret = 0;
	struct rt_thread* thread;
	rt_base_t level;

	thread = pt->polling_thread;

	timeout = rt_tick_from_millisecond(msec);

	level = rt_hw_interrupt_disable();

	if(timeout != 0 && !pt->triggered) {
		rt_thread_suspend(thread);

		if(timeout > 0) {
			rt_timer_control(&(thread->thread_timer),
			                 RT_TIMER_CTRL_SET_TIME,
			                 &timeout);
			rt_timer_start(&(thread->thread_timer));
		}

		rt_hw_interrupt_enable(level);

		rt_schedule();

		level = rt_hw_interrupt_disable();
	}

	ret = !pt->triggered;
	rt_hw_interrupt_enable(level);

	return ret;
}

static int do_pollfd(struct pollfd* pollfd, rt_pollreq_t* req)
{
	int mask = 0;
	int fd;

	fd = pollfd->fd;

	if(fd >= 0) {
		struct dfs_fd* f = fd_get(fd);
		mask = POLLNVAL;

		if(f) {
			mask = POLLMASK_DEFAULT;

			if(f->fops->poll) {
				req->_key = pollfd->events | POLLERR | POLLHUP;

				mask = f->fops->poll(f, req);
			}

			/* Mask out unneeded events. */
			mask &= pollfd->events | POLLERR | POLLHUP;
			fd_put(f);
		}
	}

	pollfd->revents = mask;

	return mask;
}

static int poll_do(struct pollfd* fds, nfds_t nfds, struct rt_poll_table* pt, int msec)
{
	int num;
	int istimeout = 0;
	int n;
	struct pollfd* pf;

	if(msec == 0) {
		pt->req._proc = RT_NULL;
		istimeout = 1;
	}

	while(1) {
		pf = fds;
		num = 0;

		for(n = 0; n < nfds; n ++) {
			if(do_pollfd(pf, &pt->req)) {
				num ++;
				pt->req._proc = RT_NULL;
			}

			pf ++;
		}

		pt->req._proc = RT_NULL;

		if(num || istimeout)
			break;

		if(poll_wait_timeout(pt, msec))
			istimeout = 1;
	}

	return num;
}

static void poll_teardown(struct rt_poll_table* pt)
{
	struct rt_poll_node* node, *next;

	next = pt->nodes;

	while(next) {
		node = next;
		rt_wqueue_remove(&node->wqn);
		next = node->next;
		rt_free(node);
	}
}

int poll(struct pollfd* fds, nfds_t nfds, int timeout)
{
	int num;
	struct rt_poll_table table;

	poll_table_init(&table);

	num = poll_do(fds, nfds, &table, timeout);

	poll_teardown(&table);

	return num;
}

